#include <func.h>

#define BACKLOG 10
#define MMAP_SIZE (4096 * 10)

int tcp_listen(const char* port)
{
    // 服务器监听 port 端口
    struct addrinfo hints;
    bzero(&hints, sizeof(hints));
    hints.ai_flags = AI_PASSIVE;    // 设置通配符地址
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;    // TCP

    struct addrinfo *result, *p;
    if (getaddrinfo(NULL, port, &hints, &result) == -1) {
        return -1;
    }

    // 遍历地址信息，绑定端口
    int sockfd = -1;
    for (p = result; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
            continue;
        }
        if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
            continue;
        }
        if (listen(sockfd, BACKLOG) == -1) {
            close(sockfd);
            continue;
        }
        // socket, bind, listen 都成功了
        break;
    }  
    // 释放地址信息
    freeaddrinfo(result);
    return sockfd;
}

void* getipaddr(struct sockaddr* addr) 
{
    if (addr->sa_family == AF_INET) {
        return &((struct sockaddr_in*)addr)->sin_addr;
    }
    return &((struct sockaddr_in6*)addr)->sin6_addr;
}

void recv_file(int connfd, const char* file)
{
    off_t fsize;
    recv(connfd, &fsize, sizeof(fsize), MSG_WAITALL);
    printf("fsize: %ld\n", fsize);

    int fd = open(file, O_RDWR | O_CREAT | O_TRUNC, 0666);
    ftruncate(fd, fsize);   // 截断文件
    
    off_t offset = 0;
    while (offset < fsize) {
        int length = (fsize - offset) >= MMAP_SIZE ?
            MMAP_SIZE : (fsize - offset);

        void* buff = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset);
        recv(connfd, buff, length, MSG_WAITALL);
        munmap(buff, length);

        offset += length;
    }
    printf("receive file success\n");
}

int main(int argc, char* argv[])
{

    char port[] = "9527";
    int listenfd = tcp_listen(port);
    if (listenfd == -1) {
        error(1, errno, "tcp_listen()");
    }
    printf("INFO: listen %s success\n", port);

    struct sockaddr_storage client_addr; 
    socklen_t addrlen = sizeof(client_addr);

    int connfd = accept(listenfd, (struct sockaddr*)&client_addr, &addrlen);

    char ipstr[50];
    inet_ntop(client_addr.ss_family, getipaddr((struct sockaddr*)&client_addr), ipstr, 50);
    printf("INFO: receive connection from %s\n", ipstr);
    
    recv_file(connfd, "b.txt");
    return 0;
}

